library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;


entity ip_to_udp is
port (
    --// clk, reset
    rst_n_i: in std_logic;
    clk_sys_i: in std_logic;
    --// received ip frame
    -- excludes mac layer header. includes ip header.
    ip_rx_data: in std_logic_vector(15 downto 0);
    ip_rx_data_valid: in std_logic;
    ip_rx_sof: in std_logic;
    ip_rx_eof: in std_logic;
    ip_byte_count: in std_logic_vector(15 downto 0);
    ip_header_flag: in std_logic;
    -- latency: 2 clks after mac_rx_data
    -- as the ip frame validity is checked on-the-fly, the user should always check if
    -- the ip_rx_data_valid is high at the end of the ip frame (ip_rx_eof) to confirm that the
    -- entire ip frame is valid. validity checks already performed (in packet_parsing) are
    -- (a) correct ip header checksum
    -- ip_byte_count is reset at the start of the data field (i.e. immediately after the header)
    -- always use ip_byte_count using the ip_header_flag context (inside or outside the ip header?)
    --// udp attributes, already parsed in packet_parsing (shared code)
    rx_udp_cksum: in std_logic_vector(16 downto 0);
    -- udp checksum (including pseudo-header).
    -- correct checksum is either x10000 or x00001
    rx_udp_cksum_rdy: in std_logic;
    -- 1 clk pulse. latency: 3 clk after receiving the last udp byte.
    --// application interface
    -- latency: 2 clks after the received ip frame.
    udp_rx_data: out std_logic_vector(15 downto 0);
    -- udp data field when udp_rx_data_valid = '1'
    udp_rx_data_valid: out std_logic;
    -- delineates the udp data field
    udp_rx_sof: out std_logic;
    -- 1 clk pulse indicating that udp_rx_data is the first byte in the udp data field.
    udp_rx_eof: out std_logic
    -- 1 clk pulse indicating that udp_rx_data is the last byte in the udp data field.
    -- always check udp_rx_data_valid at the end of packet (udp_rx_eof = '1') to confirm
    -- that the udp packet is valid. external buffer may have to backtrack to the the last
    -- valid pointer to discard an invalid udp packet.
    -- reason: we only knows about bad udp packets at the end.
);
end entity;

architecture behavioral of ip_to_udp is
--------------------------------------------------------
--      components
--------------------------------------------------------
--------------------------------------------------------
--     signals
--------------------------------------------------------

--// udp 16-bit word -----------------------------
signal ip_rx_data_previous: std_logic_vector(15 downto 0) := (others => '0');
signal udp_byte_count: std_logic_vector(15 downto 0) := (others => '0');

--// check udp validity -----------------------------
signal valid_rx_udp: std_logic := '0';
signal valid_rx_udp_cksum: std_logic := '0';
signal ip_rx_eof_d: std_logic := '0';

--// copy udp data to buffer ------------------------
signal rx_udp_size: std_logic_vector(15 downto 0) := (others => '0');
signal udp_rx_data_e: std_logic_vector(15 downto 0) := (others => '0');
signal udp_rx_data_valid_e: std_logic := '0';
signal udp_rx_sof_e: std_logic  := '0';
signal udp_rx_eof_e: std_logic  := '0';
--------------------------------------------------------
--      implementation
--------------------------------------------------------
begin

udp_byte_count <= ip_byte_count;    -- valid outside the ip header, inside the udp frame.

--// udp 16-bit word -----------------------------
-- reconstruct 16-bit words
udp_previous_byte_001: process(clk_sys_i)
begin
    if rising_edge(clk_sys_i) then
        if(ip_rx_data_valid = '1') and (ip_header_flag = '0') then
            -- udp frame
            ip_rx_data_previous <= ip_rx_data;    -- remember previous byte (useful when reading 16-bit fields)
        end if;
    end if;
end process;

--// check udp validity -----------------------------
-- the udp packet reception is immediately cancelled if
-- (f) udp checksum is incorrect
-- aligned with ip_rx_data_valid_d
validity_check_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        valid_rx_udp <= '1';
        ip_rx_eof_d <= '0';
    else
        ip_rx_eof_d <= ip_rx_eof;    -- read valid_rx_udp at the end of the packet.
        if(ip_rx_sof = '1') then
            -- just received first word. valid udp datagram until proven otherwise
            valid_rx_udp <= '1';
        elsif(ip_rx_eof = '1') and (ip_rx_data_valid = '0') then
            -- invalid ip frame reported at the end of the ip frame.
            valid_rx_udp <= '0';
        end if;
    end if;
end if;
end process;

-- late arrival (one clk after ip_rx_eof)
valid_rx_udp_cksum <= '0' when (rx_udp_cksum_rdy = '1') and not((rx_udp_cksum = "1" & x"0000") or (rx_udp_cksum = "0" & x"0001") )
                 else '1';
    --(f) udp checksum is incorrect
    -- note: this check is performed while reading the last udp byte. the outcome is available
    -- one clock after the udp_rx_eof

-- prepare output
-- keep track of the actual number of udp bytes (otherwise we may forward dummy data
-- when the datagram is small and padded for minimum length).
-- aligned with ip_rx_data_valid_d
output_gen_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        udp_rx_data_e <= (others => '0');
        udp_rx_data_valid_e <= '0';
        udp_rx_sof_e <= '0';
        udp_rx_eof_e <= '0';
    else
        if(ip_rx_data_valid = '1') and (udp_byte_count = 4) and (ip_header_flag = '0') then
            -- udp datagram size.
            rx_udp_size <= (ip_rx_data - 8);    -- subtract the udp header (8 bytes).
            udp_rx_data_valid_e <= '0';
            udp_rx_sof_e <= '0';
            udp_rx_eof_e <= '0';
        elsif(ip_rx_data_valid = '1') and (ip_header_flag = '0') and (udp_byte_count >= 8) and (rx_udp_size /= 0) then
            -- udp header is always 8 bytes. data field starts at byte 8.
            -- write byte to buffer as long as there is meaningful and valid udp data, not filler.
            udp_rx_data_e <= ip_rx_data;
            udp_rx_data_valid_e <= '1';
            rx_udp_size <= rx_udp_size - 2;
            if(udp_byte_count = 8) then
                udp_rx_sof_e <= '1';
            else
                udp_rx_sof_e <= '0';
            end if;
            if(rx_udp_size = 2) then
                udp_rx_eof_e <= '1';
            else
                udp_rx_eof_e <= '0';
            end if;
        else
            udp_rx_data_valid_e <= '0';
            udp_rx_sof_e <= '0';
            udp_rx_eof_e <= '0';
        end if;
    end if;
end if;
end process;

-- delay udp output because we have to wait one more clk for the udp checksum outcome.
-- aligned with ip_rx_data_valid_d2
output_gen_002: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        udp_rx_data <= (others => '0');
        udp_rx_data_valid <= '0';
        udp_rx_sof <= '0';
        udp_rx_eof <= '0';
    else
        udp_rx_data <= udp_rx_data_e;
        --udp_rx_data_valid <= udp_rx_data_valid_e and valid_rx_udp and valid_rx_udp_cksum;
        udp_rx_data_valid <= udp_rx_data_valid_e and valid_rx_udp;
        udp_rx_sof <= udp_rx_sof_e and valid_rx_udp;    -- no need for sof if udp frame is invalid
        udp_rx_eof <= udp_rx_eof_e;
    end if;
end if;
end process;

end behavioral;
